﻿using System;
using System.Collections.Generic;
using HYCodeBase.Algorithm.Octree.Data;

namespace HYCodeBase.Algorithm.Octree
{
	public class BoundsOctree<T>
	{
		public int Count { get; private set; }

		public BoundingBox MaxBounds
		{
			get
			{
				return this._rootNode.Bounds;
			}
		}

		public BoundsOctree(float initialWorldSize, Point initialWorldPos, float minNodeSize, float loosenessVal)
		{
			if (minNodeSize > initialWorldSize)
			{
				minNodeSize = initialWorldSize;
			}
			this.Count = 0;
			this._initialSize = initialWorldSize;
			this._minSize = minNodeSize;
			this._looseness = MathExtensions.Clamp(loosenessVal, 1f, 2f);
			this._rootNode = new BoundsOctree<T>.Node(this._initialSize, this._minSize, loosenessVal, initialWorldPos);
		}

		public void Add(T obj, BoundingBox objBounds)
		{
			int num = 0;
			while (!this._rootNode.Add(obj, objBounds))
			{
				this.Grow(objBounds.Center - this._rootNode.Center);
				if (++num > 20)
				{
					return;
				}
			}
			int count = this.Count;
			this.Count = count + 1;
		}

		public bool Remove(T obj)
		{
			bool flag = this._rootNode.Remove(obj);
			if (flag)
			{
				int count = this.Count;
				this.Count = count - 1;
				this.Shrink();
			}
			return flag;
		}

		public bool Remove(T obj, BoundingBox objBounds)
		{
			bool flag = this._rootNode.Remove(obj, objBounds);
			if (flag)
			{
				int count = this.Count;
				this.Count = count - 1;
				this.Shrink();
			}
			return flag;
		}

		public bool IsColliding(BoundingBox checkBounds)
		{
			return this._rootNode.IsColliding(ref checkBounds);
		}

		public bool IsColliding(Ray checkRay, float maxDistance)
		{
			return this._rootNode.IsColliding(ref checkRay, maxDistance);
		}

		public void GetColliding(List<T> collidingWith, BoundingBox checkBounds)
		{
			this._rootNode.GetColliding(ref checkBounds, collidingWith);
		}

		public void GetColliding(List<T> collidingWith, Ray checkRay, float maxDistance = float.PositiveInfinity)
		{
			this._rootNode.GetColliding(ref checkRay, collidingWith, maxDistance);
		}

		private void Grow(Point direction)
		{
			int num = (direction.X >= 0f) ? 1 : -1;
			int num2 = (direction.Y >= 0f) ? 1 : -1;
			int num3 = (direction.Z >= 0f) ? 1 : -1;
			BoundsOctree<T>.Node rootNode = this._rootNode;
			float num4 = this._rootNode.BaseLength / 2f;
			float baseLengthVal = this._rootNode.BaseLength * 2f;
			Point point = this._rootNode.Center + new Point((float)num * num4, (float)num2 * num4, (float)num3 * num4);
			this._rootNode = new BoundsOctree<T>.Node(baseLengthVal, this._minSize, this._looseness, point);
			if (rootNode.HasAnyObjects())
			{
				int rootPosIndex = BoundsOctree<T>.GetRootPosIndex(num, num2, num3);
				BoundsOctree<T>.Node[] array = new BoundsOctree<T>.Node[8];
				for (int i = 0; i < 8; i++)
				{
					if (i == rootPosIndex)
					{
						array[i] = rootNode;
					}
					else
					{
						num = ((i % 2 == 0) ? -1 : 1);
						num2 = ((i > 3) ? -1 : 1);
						num3 = ((i < 2 || (i > 3 && i < 6)) ? -1 : 1);
						array[i] = new BoundsOctree<T>.Node(this._rootNode.BaseLength, this._minSize, this._looseness, point + new Point((float)num * num4, (float)num2 * num4, (float)num3 * num4));
					}
				}
				this._rootNode.SetChildren(array);
			}
		}

		private void Shrink()
		{
			this._rootNode = this._rootNode.ShrinkIfPossible(this._initialSize);
		}

		private static int GetRootPosIndex(int xDir, int yDir, int zDir)
		{
			int num = (xDir > 0) ? 1 : 0;
			if (yDir < 0)
			{
				num += 4;
			}
			if (zDir > 0)
			{
				num += 2;
			}
			return num;
		}

		private BoundsOctree<T>.Node _rootNode;

		private readonly float _looseness;

		private readonly float _initialSize;

		private readonly float _minSize;

		private class Node
		{
			public Point Center { get; private set; }

			public float BaseLength { get; private set; }

			public BoundingBox Bounds
			{
				get
				{
					return this._bounds;
				}
			}

			public Node(float baseLengthVal, float minSizeVal, float loosenessVal, Point centerVal)
			{
				this.SetValues(baseLengthVal, minSizeVal, loosenessVal, centerVal);
			}

			public bool Add(T obj, BoundingBox objBounds)
			{
				if (!BoundsOctree<T>.Node.Encapsulates(this._bounds, objBounds))
				{
					return false;
				}
				this.SubAdd(obj, objBounds);
				return true;
			}

			public bool Remove(T obj)
			{
				bool flag = false;
				for (int i = 0; i < this._objects.Count; i++)
				{
					if (this._objects[i].Obj.Equals(obj))
					{
						flag = this._objects.Remove(this._objects[i]);
						break;
					}
				}
				if (!flag && this._children != null)
				{
					for (int j = 0; j < 8; j++)
					{
						flag = this._children[j].Remove(obj);
						if (flag)
						{
							break;
						}
					}
				}
				if (flag && this._children != null && this.ShouldMerge())
				{
					this.Merge();
				}
				return flag;
			}

			public bool Remove(T obj, BoundingBox objBounds)
			{
				return BoundsOctree<T>.Node.Encapsulates(this._bounds, objBounds) && this.SubRemove(obj, objBounds);
			}

			public bool IsColliding(ref BoundingBox checkBounds)
			{
				if (!this._bounds.Intersects(checkBounds))
				{
					return false;
				}
				for (int i = 0; i < this._objects.Count; i++)
				{
					if (this._objects[i].Bounds.Intersects(checkBounds))
					{
						return true;
					}
				}
				if (this._children != null)
				{
					for (int j = 0; j < 8; j++)
					{
						if (this._children[j].IsColliding(ref checkBounds))
						{
							return true;
						}
					}
				}
				return false;
			}

			public bool IsColliding(ref Ray checkRay, float maxDistance = float.PositiveInfinity)
			{
				float num;
				if (!this._bounds.IntersectRay(checkRay, out num) || num > maxDistance)
				{
					return false;
				}
				for (int i = 0; i < this._objects.Count; i++)
				{
					if (this._objects[i].Bounds.IntersectRay(checkRay, out num) && num <= maxDistance)
					{
						return true;
					}
				}
				if (this._children != null)
				{
					for (int j = 0; j < 8; j++)
					{
						if (this._children[j].IsColliding(ref checkRay, maxDistance))
						{
							return true;
						}
					}
				}
				return false;
			}

			public void GetColliding(ref BoundingBox checkBounds, List<T> result)
			{
				if (!this._bounds.Intersects(checkBounds))
				{
					return;
				}
				for (int i = 0; i < this._objects.Count; i++)
				{
					if (this._objects[i].Bounds.Intersects(checkBounds))
					{
						result.Add(this._objects[i].Obj);
					}
				}
				if (this._children != null)
				{
					for (int j = 0; j < 8; j++)
					{
						this._children[j].GetColliding(ref checkBounds, result);
					}
				}
			}

			public void GetColliding(ref Ray checkRay, List<T> result, float maxDistance = float.PositiveInfinity)
			{
				float num;
				if (!this._bounds.IntersectRay(checkRay, out num) || num > maxDistance)
				{
					return;
				}
				for (int i = 0; i < this._objects.Count; i++)
				{
					if (this._objects[i].Bounds.IntersectRay(checkRay, out num) && num <= maxDistance)
					{
						result.Add(this._objects[i].Obj);
					}
				}
				if (this._children != null)
				{
					for (int j = 0; j < 8; j++)
					{
						this._children[j].GetColliding(ref checkRay, result, maxDistance);
					}
				}
			}

			public void SetChildren(BoundsOctree<T>.Node[] childOctrees)
			{
				if (childOctrees.Length != 8)
				{
					return;
				}
				this._children = childOctrees;
			}

			public BoundsOctree<T>.Node ShrinkIfPossible(float minLength)
			{
				if (this.BaseLength < 2f * minLength)
				{
					return this;
				}
				if (this._objects.Count == 0 && (this._children == null || this._children.Length == 0))
				{
					return this;
				}
				int num = -1;
				for (int i = 0; i < this._objects.Count; i++)
				{
					BoundsOctree<T>.Node.OctreeObject octreeObject = this._objects[i];
					int num2 = this.BestFitChild(octreeObject.Bounds);
					if (i != 0 && num2 != num)
					{
						return this;
					}
					if (!BoundsOctree<T>.Node.Encapsulates(this._childBounds[num2], octreeObject.Bounds))
					{
						return this;
					}
					if (num < 0)
					{
						num = num2;
					}
				}
				if (this._children != null)
				{
					bool flag = false;
					for (int j = 0; j < this._children.Length; j++)
					{
						if (this._children[j].HasAnyObjects())
						{
							if (flag)
							{
								return this;
							}
							if (num >= 0 && num != j)
							{
								return this;
							}
							flag = true;
							num = j;
						}
					}
				}
				if (this._children == null)
				{
					this.SetValues(this.BaseLength / 2f, this._minSize, this._looseness, this._childBounds[num].Center);
					return this;
				}
				if (num == -1)
				{
					return this;
				}
				return this._children[num];
			}

			public bool HasAnyObjects()
			{
				if (this._objects.Count > 0)
				{
					return true;
				}
				if (this._children != null)
				{
					for (int i = 0; i < 8; i++)
					{
						if (this._children[i].HasAnyObjects())
						{
							return true;
						}
					}
				}
				return false;
			}

			private void SetValues(float baseLengthVal, float minSizeVal, float loosenessVal, Point centerVal)
			{
				this.BaseLength = baseLengthVal;
				this._minSize = minSizeVal;
				this._looseness = loosenessVal;
				this.Center = centerVal;
				this._adjLength = this._looseness * baseLengthVal;
				Point size = new Point(this._adjLength, this._adjLength, this._adjLength);
				this._bounds = new BoundingBox(this.Center, size);
				float num = this.BaseLength / 4f;
				float num2 = this.BaseLength / 2f * this._looseness;
				Point size2 = new Point(num2, num2, num2);
				this._childBounds = new BoundingBox[8];
				this._childBounds[0] = new BoundingBox(this.Center + new Point(-num, num, -num), size2);
				this._childBounds[1] = new BoundingBox(this.Center + new Point(num, num, -num), size2);
				this._childBounds[2] = new BoundingBox(this.Center + new Point(-num, num, num), size2);
				this._childBounds[3] = new BoundingBox(this.Center + new Point(num, num, num), size2);
				this._childBounds[4] = new BoundingBox(this.Center + new Point(-num, -num, -num), size2);
				this._childBounds[5] = new BoundingBox(this.Center + new Point(num, -num, -num), size2);
				this._childBounds[6] = new BoundingBox(this.Center + new Point(-num, -num, num), size2);
				this._childBounds[7] = new BoundingBox(this.Center + new Point(num, -num, num), size2);
			}

			private void SubAdd(T obj, BoundingBox objBounds)
			{
				if (this._objects.Count < 8 || this.BaseLength / 2f < this._minSize)
				{
					BoundsOctree<T>.Node.OctreeObject item = new BoundsOctree<T>.Node.OctreeObject
					{
						Obj = obj,
						Bounds = objBounds
					};
					this._objects.Add(item);
					return;
				}
				int num;
				if (this._children == null)
				{
					this.Split();
					if (this._children == null)
					{
						return;
					}
					for (int i = this._objects.Count - 1; i >= 0; i--)
					{
						BoundsOctree<T>.Node.OctreeObject octreeObject = this._objects[i];
						num = this.BestFitChild(octreeObject.Bounds);
						if (BoundsOctree<T>.Node.Encapsulates(this._children[num]._bounds, octreeObject.Bounds))
						{
							this._children[num].SubAdd(octreeObject.Obj, octreeObject.Bounds);
							this._objects.Remove(octreeObject);
						}
					}
				}
				num = this.BestFitChild(objBounds);
				if (BoundsOctree<T>.Node.Encapsulates(this._children[num]._bounds, objBounds))
				{
					this._children[num].SubAdd(obj, objBounds);
					return;
				}
				BoundsOctree<T>.Node.OctreeObject item2 = new BoundsOctree<T>.Node.OctreeObject
				{
					Obj = obj,
					Bounds = objBounds
				};
				this._objects.Add(item2);
			}

			private bool SubRemove(T obj, BoundingBox objBounds)
			{
				bool flag = false;
				for (int i = 0; i < this._objects.Count; i++)
				{
					if (this._objects[i].Obj.Equals(obj))
					{
						flag = this._objects.Remove(this._objects[i]);
						break;
					}
				}
				if (!flag && this._children != null)
				{
					int num = this.BestFitChild(objBounds);
					flag = this._children[num].SubRemove(obj, objBounds);
				}
				if (flag && this._children != null && this.ShouldMerge())
				{
					this.Merge();
				}
				return flag;
			}

			private void Split()
			{
				float num = this.BaseLength / 4f;
				float baseLengthVal = this.BaseLength / 2f;
				this._children = new BoundsOctree<T>.Node[8];
				this._children[0] = new BoundsOctree<T>.Node(baseLengthVal, this._minSize, this._looseness, this.Center + new Point(-num, num, -num));
				this._children[1] = new BoundsOctree<T>.Node(baseLengthVal, this._minSize, this._looseness, this.Center + new Point(num, num, -num));
				this._children[2] = new BoundsOctree<T>.Node(baseLengthVal, this._minSize, this._looseness, this.Center + new Point(-num, num, num));
				this._children[3] = new BoundsOctree<T>.Node(baseLengthVal, this._minSize, this._looseness, this.Center + new Point(num, num, num));
				this._children[4] = new BoundsOctree<T>.Node(baseLengthVal, this._minSize, this._looseness, this.Center + new Point(-num, -num, -num));
				this._children[5] = new BoundsOctree<T>.Node(baseLengthVal, this._minSize, this._looseness, this.Center + new Point(num, -num, -num));
				this._children[6] = new BoundsOctree<T>.Node(baseLengthVal, this._minSize, this._looseness, this.Center + new Point(-num, -num, num));
				this._children[7] = new BoundsOctree<T>.Node(baseLengthVal, this._minSize, this._looseness, this.Center + new Point(num, -num, num));
			}

			private void Merge()
			{
				for (int i = 0; i < 8; i++)
				{
					BoundsOctree<T>.Node node = this._children[i];
					for (int j = node._objects.Count - 1; j >= 0; j--)
					{
						BoundsOctree<T>.Node.OctreeObject item = node._objects[j];
						this._objects.Add(item);
					}
				}
				this._children = null;
			}

			private static bool Encapsulates(BoundingBox outerBounds, BoundingBox innerBounds)
			{
				return outerBounds.Contains(innerBounds.Min) && outerBounds.Contains(innerBounds.Max);
			}

			private int BestFitChild(BoundingBox objBounds)
			{
				return ((objBounds.Center.X <= this.Center.X) ? 0 : 1) + ((objBounds.Center.Y >= this.Center.Y) ? 0 : 4) + ((objBounds.Center.Z <= this.Center.Z) ? 0 : 2);
			}

			private bool ShouldMerge()
			{
				int num = this._objects.Count;
				if (this._children != null)
				{
					foreach (BoundsOctree<T>.Node node in this._children)
					{
						if (node._children != null)
						{
							return false;
						}
						num += node._objects.Count;
					}
				}
				return num <= 8;
			}

			private float _looseness;

			private float _minSize;

			private float _adjLength;

			private BoundingBox _bounds;

			private readonly List<BoundsOctree<T>.Node.OctreeObject> _objects = new List<BoundsOctree<T>.Node.OctreeObject>();

			private BoundsOctree<T>.Node[] _children;

			private BoundingBox[] _childBounds;

			private const int NumObjectsAllowed = 8;

			private class OctreeObject
			{
				public T Obj;

				public BoundingBox Bounds;
			}
		}
	}
}
